package com.gitee.lichoking.mysoap.common;

import com.gitee.lichoking.mysoap.annotation.Param;
import com.gitee.lichoking.mysoap.convert.DefaultConverterLoader;
import com.gitee.lichoking.mysoap.util.SoapUtils;
import org.beetl.core.Template;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;

/**
 * mapper代理类
 *
 * @Author lich
 * @Date 2021/5/24
 **/
public class MapperProxy<T> implements InvocationHandler {
    private final Configuration configuration;
    private Class<T> mapperInterface;


    public MapperProxy(Configuration configuration, Class<T> mapperInterface) {
        this.configuration = configuration;
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 继承自Object的方法
        if (Object.class.equals(method.getDeclaringClass())) {
            // 直接执行原有方法
            return method.invoke(this, args);
            // 默认方法
        } else if (method.isDefault()) {
            // 执行默认方法
            return invokeDefaultMethod(proxy, method, args);
        }

        Map<String, XmlTemplate> xmlTemplateCache = configuration.getXmlTemplateCache();
        String mapperInterfaceName = mapperInterface.getName();
        String methodName = method.getName();
        String xmlTemplateKey = new StringBuilder(mapperInterfaceName).append(".").append(methodName).toString();
        XmlTemplate xmlTemplate = xmlTemplateCache.get(xmlTemplateKey);
        if (xmlTemplate == null) {
            throw new SoapException("找不到" + mapperInterfaceName + "模板");
        }
        Template template = configuration.getGroupTemplate().getTemplate(xmlTemplate.getBody());

        Map<Class<?>, Map<String, Method>> parameterFieldCache = configuration.getParameterFieldCache();
        Parameter[] parameters = method.getParameters();
        int parametersLength = parameters.length;
        if (parametersLength == 1 && !DefaultConverterLoader.hasConverter(parameters[0].getType())) {
            //只有一个参数，并且是复杂bean，尝试从get方法获取参数名
            Map<String, Method> fieldGetters = parameterFieldCache.get(parameters[0].getType());
            for (String fieldName : fieldGetters.keySet()) {
                Method fieldGetter = fieldGetters.get(fieldName);
                Object paramObj = args[0];
                template.binding(fieldName, fieldGetter.invoke(paramObj));
            }
        } else {
            //从@Param获取参数名
            for (int i = 0; i < parametersLength; i++) {
                if (parameters[i].isAnnotationPresent(Param.class)) {
                    Param paramAnnotation = parameters[i].getAnnotation(Param.class);
                    String paramName = paramAnnotation.value();
                    template.binding(paramName, args[i]);
                } else {
                    throw new SoapException("请求参数缺少@Param注解");
                }
            }
        }

        //渲染结果
        String requestXml = template.render();
        return SoapUtils.request2Object(configuration.getSoapUrl(), configuration.getSoapAction(), requestXml, method.getReturnType());
    }

    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor
                .newInstance(declaringClass,
                        MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
                .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }
}
